﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;
using System.Runtime.Remoting.Messaging;
using System.Net;

namespace EkspSys.Network
{
    public class NetworkInterface
    {
        public string name;
        public string description;
        public uint flags;
    }

    public enum Protocol { TCP, UDP };

    public class Package
    {
        public Protocol protocol;
        public IPAddress srcAddr;
        public ushort srcPort;
        public IPAddress destAdr;
        public ushort destPort;

        // Maybe some more...
    }

    public delegate void PackageSniffedEventHandler(Package pck);

    class WinPcap
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct pcap_if
        {
            public IntPtr next;
            public string name;
            public string description;
            public pcap_addr addresses;
            public uint flags;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct pcap_pkthdr
        {
            public timeval ts;
            public uint caplen;
            public uint len;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct timeval
        {
            public int tv_sec;
            public int tv_usec;
        }

        /*
         * IP header as described by wikipedia.
         */
        [StructLayout(LayoutKind.Sequential)]
        private struct ip_header
        {
            public byte ver_ihl;            // Version and Internet Header Length
            public byte tos;                // Type of service
            public ushort len;              // Total length
            public ushort indentification;  // Identification
            public ushort flags_fragoff;    // Flags and fragment offset
            public byte ttl;                // Time to live
            public byte protocol;           // Protocol
            public ushort hdrcsum;          // Header checksum
            public uint srcip;              // Source IP
            public uint destip;             // Destination IP
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct udp_header
        {
            public ushort srcport;           // Source port
            public ushort destport;          // Destination port
            public ushort len;               // Length
            public ushort csum;              // Checksum
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct tcp_header
        {
            public ushort srcport;           // Source port
            public ushort destport;          // Destination port
            public uint seqnr;               // Sequence number
            public uint acknr;               // Acknowledgement number
            public ushort off_flags;         // Offset and flags
            public ushort winsz;             // Window size
            public ushort csum;              // Checksum
            public ushort urgptr;            // Urgent pointer
        }

        private struct pcap_addr
        {
            // Something...
        }

        // TODO: I think errBuf is incorrectly defined - it should be a pointer
        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int pcap_findalldevs_ex(string source, IntPtr auth, ref IntPtr alldevs, ref string errBuf);

        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern void pcap_freealldevs(IntPtr devList);

        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr pcap_open(string source, int snaplen, int flags, int read_timeout, IntPtr auth, ref string errBuf);

        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern void pcap_close(IntPtr handle);

        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int pcap_next_ex(IntPtr handle, ref IntPtr hdr, ref IntPtr data);

        [DllImport("wpcap.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int pcap_datalink(IntPtr handle);

        private static uint readDWORD(byte[] buf, ref int pos)
        {
            pos += 4;
            return (uint)(((uint)buf[pos - 1]) << 24) + (((uint)buf[pos - 2]) << 16) + (((uint)buf[pos - 3]) << 8) + ((uint)buf[pos - 4]);
        }

        private delegate void ListenerHandler(NetworkInterface net_if, AsyncOperation operation);

        public event PackageSniffedEventHandler PackageSniffed;

        private SendOrPostCallback onPackageSniffedDelegate;

        public WinPcap()
        {
            onPackageSniffedDelegate = new SendOrPostCallback(PackageSniffedDlgt);
        }

        private void PackageSniffedDlgt(object obj)
        {
            if (PackageSniffed != null)
            {
                Package pck = obj as Package;
                PackageSniffed(pck);
            }
        }

        private ListenerHandler dlgt;
        private IAsyncResult asyncResult;

        public void StartCaptureAsync(NetworkInterface net_if)
        {
            lock (isAliveLock)
            {
                if (isAlive == true)
                    throw new ApplicationException("The capture is already running.");
                isAlive = true;
            }

            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
            dlgt = new ListenerHandler(StartListening);
            asyncResult = dlgt.BeginInvoke(net_if, asyncOp, null, null);
        }

        public void StopCapture()
        {
            lock (isAliveLock)
            {
                if (isAlive == true)
                {
                    isAlive = false;
                    dlgt.EndInvoke(asyncResult);
                }
            }
        }

        public bool IsAlive()
        {
            return isAlive;
        }

        private Object isAliveLock = new Object();
        private bool isAlive = false;

        private const int PROTO_TCP = 6;
        private const int PROTO_UDP = 17;

        private void StartListening(NetworkInterface net_if, AsyncOperation asyncOp)
        {
            string errBuf = "";
            IntPtr pcapHandle = pcap_open(net_if.name, 65536, 1, 1000, IntPtr.Zero, ref errBuf);
            if (pcapHandle == IntPtr.Zero)
            {
                throw new ApplicationException("Failed to open pcap handle.");
            }

            // Only support ethernet!
            if (pcap_datalink(pcapHandle) != 1)
            {
                throw new ApplicationException("This application only supports ethernet.");
            }

            // TODO: Add filters so only TCP and UDP packages are sniffed.

            IntPtr hdrPtr = IntPtr.Zero;
            IntPtr dataPtr = IntPtr.Zero;
            int res;
            while (isAlive && (res = pcap_next_ex(pcapHandle, ref hdrPtr, ref dataPtr)) >= 0)
            {
                if (res == 0)
                {
                    // Timeout
                    continue;
                }

                pcap_pkthdr pckhdr = (pcap_pkthdr)Marshal.PtrToStructure(hdrPtr, typeof(pcap_pkthdr));

                // Ethernet frame len
                const ushort ETHERNET_FRAME_LEN = 14;
                ip_header iphdr = (ip_header)Marshal.PtrToStructure(IntPtr.Add(dataPtr, ETHERNET_FRAME_LEN), typeof(ip_header));

                // Internet Header Length
                int ihl = Convert.ToInt32(iphdr.ver_ihl & (byte)0xF) * 4;
                IntPtr protoHdrPtr = IntPtr.Add(dataPtr, ETHERNET_FRAME_LEN + ihl);

                Package pck = new Package();
                pck.destAdr = new IPAddress(iphdr.destip);
                pck.srcAddr = new IPAddress(iphdr.srcip);

                if (iphdr.protocol == PROTO_TCP)
                {
                    pck.protocol = Protocol.TCP;
                    tcp_header tcphdr = (tcp_header)Marshal.PtrToStructure(protoHdrPtr, typeof(tcp_header));

                    pck.destPort = tcphdr.destport;
                    pck.srcPort = tcphdr.srcport;
                }
                else if (iphdr.protocol == PROTO_UDP)
                {
                    pck.protocol = Protocol.UDP;
                    udp_header udphdr = (udp_header)Marshal.PtrToStructure(protoHdrPtr, typeof(udp_header));

                    pck.destPort = udphdr.destport;
                    pck.srcPort = udphdr.srcport;
                }
                else
                {
                    // We do not support this protocol...
                }

                asyncOp.Post(onPackageSniffedDelegate, pck);

                hdrPtr = IntPtr.Zero;
                dataPtr = IntPtr.Zero;
            }

            pcap_close(pcapHandle);
        }

        public static List<NetworkInterface> GetNetworkInterfaces()
        {
            String errBuf = "";
            IntPtr devListPtr = new IntPtr();
            if (pcap_findalldevs_ex("rpcap://", IntPtr.Zero, ref devListPtr, ref errBuf) != 0)
            {
                throw new System.ApplicationException("Failed to find network device list...");
            }

            List<NetworkInterface> interfaces = new List<NetworkInterface>();
            IntPtr curDev = devListPtr;
            while (curDev != IntPtr.Zero)
            {
                pcap_if wrap_net_if = (pcap_if)Marshal.PtrToStructure(curDev, typeof(pcap_if));
                curDev = wrap_net_if.next;

                NetworkInterface net_if = new NetworkInterface();
                net_if.name = wrap_net_if.name;
                net_if.description = wrap_net_if.description;
                net_if.flags = wrap_net_if.flags;
                interfaces.Add(net_if);
            }

            pcap_freealldevs(devListPtr);

            return interfaces;
        }
    }
}
